//
//  RKActivityPresenter.swift
//  SwiftBase
//
//  Created by rubick on 2023/2/13.
//

import UIKit
import Foundation
import NVActivityIndicatorView

final class RKActivityData {
    let size: CGSize
    let message: String?
    let messageFont: UIFont
    let messageSpacing: CGFloat
    let type: NVActivityIndicatorType
    let color: UIColor
    let textColor: UIColor
    let padding: CGFloat
    let displayTimeThreshold: Int
    let minimumDisplayTime: Int
    let backgroundColor: UIColor
    public init(size: CGSize? = nil,
                message: String? = nil,
                messageFont: UIFont? = nil,
                messageSpacing: CGFloat? = nil,
                type: NVActivityIndicatorType? = nil,
                color: UIColor? = nil,
                textColor: UIColor? = nil,
                padding: CGFloat? = nil,
                displayTimeThreshold: Int? = nil,
                minimumDisplayTime: Int? = nil,
                backgroundColor: UIColor? = nil) {
        self.size = size ?? CGSize(width: 60, height: 60)
        self.message = message
        self.messageFont = messageFont ?? UIFont.boldSystemFont(ofSize: 20)
        self.messageSpacing = messageSpacing ?? CGFloat(8.0)
        self.type = type ?? .ballSpinFadeLoader
        self.color = color ?? UIColor.white
        self.textColor = textColor ?? UIColor.white
        self.padding = padding ?? 0
        self.displayTimeThreshold = displayTimeThreshold ?? 0
        self.minimumDisplayTime = minimumDisplayTime ?? 0
        self.backgroundColor = backgroundColor ?? UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
    }
}
private protocol RKActivityPresenterState {
    func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?)
    func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?)
}
private struct RKActivityPresenterStateWaitingToStart: RKActivityPresenterState {
    func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?) {
        guard let activityData = presenter.data else { return }
        presenter.show(with: activityData, fadeInAnimation)
        presenter.state = .animating
        presenter.waitingToStartGroup.leave()
    }
    func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?) {
        presenter.state = .stopped
        presenter.waitingToStartGroup.leave()
    }
}
private struct RKActivityPresenterStateAnimation: RKActivityPresenterState {
    func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?) {
        //
    }
    func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?) {
        guard let activityData = presenter.data else { return }
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.minimumDisplayTime)) {
            guard presenter.state == .waitingToStop else { return }
            presenter.stopAnimating(fadeOutAnimation)
        }
        presenter.state = .waitingToStop
    }
}
private struct RKActivityPresenterStateWaitingToStop: RKActivityPresenterState {
    func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?) {
        presenter.stopAnimating(nil)
        guard let activityData = presenter.data else { return }
        presenter.startAnimating(activityData, fadeInAnimation)
    }
    func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?) {
        presenter.hide(fadeOutAnimation)
        presenter.state = .stopped
    }
}
private struct RKActivityPresenterStateStopped: RKActivityPresenterState {
    func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?) {
        guard let activityData = presenter.data else { return }
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(activityData.displayTimeThreshold)) {
            guard presenter.state == .waitingToStart else { return }

            presenter.startAnimating(activityData, fadeInAnimation)
        }
        presenter.state = .waitingToStart
        presenter.waitingToStartGroup.enter()
    }
    func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?) {
        //
    }
}

final class RKActivityPresenter {
    fileprivate enum State: RKActivityPresenterState {
        case waitingToStart
        case animating
        case waitingToStop
        case stopped
        //
        var performer: RKActivityPresenterState {
            switch self {
            case .waitingToStart: return RKActivityPresenterStateWaitingToStart()
            case .animating: return RKActivityPresenterStateAnimation()
            case .waitingToStop: return RKActivityPresenterStateWaitingToStop()
            case .stopped: return RKActivityPresenterStateStopped()
            }
        }
        //
        func startAnimating(presenter: RKActivityPresenter, _ fadeInAnimation: FadeInAnimation?) {
            performer.startAnimating(presenter: presenter, fadeInAnimation)
        }
        //
        func stopAnimating(presenter: RKActivityPresenter, _ fadeOutAnimation: FadeOutAnimation?) {
            performer.stopAnimating(presenter: presenter, fadeOutAnimation)
        }
    }
    private let restorationIdentifier = "RKActivityIndicatorViewContainer"
    private let messageLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    //
    fileprivate var state: State = .stopped
    fileprivate var data: RKActivityData?
    fileprivate let waitingToStartGroup = DispatchGroup()
    public static let sharedInstance = RKActivityPresenter()
    public var isAnimating: Bool { return state == .animating || state == .waitingToStop }
    private init() {}
    
    // MARK:  - public interface
    public final func startAnimating(_ data: RKActivityData, _ fadeInAnimation: FadeInAnimation? = nil) {
        self.data = data
        state.startAnimating(presenter: self, fadeInAnimation)
    }
    public final func stopAnimating(_ fadeOutAnimation: FadeOutAnimation? = nil) {
        state.stopAnimating(presenter: self, fadeOutAnimation)
    }
    public final func setMessage(_ message: String?) {
        waitingToStartGroup.notify(queue: DispatchQueue.main) {
            self.messageLabel.text = message
        }
    }
    // MARK: helpers
    fileprivate func show(with activityData: RKActivityData, _ fadeInAnimation: FadeInAnimation?) {
        let containerView = UIView(frame: CGRect(x: 0, y: 0, width: rk_screenWidth, height: rk_screenHeight))
        containerView.backgroundColor = activityData.backgroundColor
        containerView.restorationIdentifier = restorationIdentifier
        containerView.translatesAutoresizingMaskIntoConstraints = false
        fadeInAnimation?(containerView)
        
        let activityIndicatorView = NVActivityIndicatorView(
            frame: CGRect(x: 0, y: 0, width: activityData.size.width, height: activityData.size.height),
            type: activityData.type,
            color: activityData.color,
            padding: activityData.padding
        )
        activityIndicatorView.startAnimating()
        activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(activityIndicatorView)
        ({
            let xConstraint = NSLayoutConstraint(item: containerView, attribute: .centerX, relatedBy: .equal, toItem: activityIndicatorView, attribute: .centerX, multiplier: 1, constant: 0)
            let yConstraint = NSLayoutConstraint(item: containerView, attribute: .centerY, relatedBy: .equal, toItem: activityIndicatorView, attribute: .centerY, multiplier: 1, constant: 0)
            containerView.addConstraints([xConstraint, yConstraint])
        }())
        messageLabel.font = activityData.messageFont
        messageLabel.textColor = activityData.textColor
        messageLabel.text = activityData.message
        containerView.addSubview(messageLabel)
        ({
            let leadingConstraint = NSLayoutConstraint(item: containerView, attribute: .leading, relatedBy: .equal, toItem: messageLabel, attribute: .leading, multiplier: 1, constant: -8)
            let trailingConstraint = NSLayoutConstraint(item: containerView, attribute: .trailing, relatedBy: .equal, toItem: messageLabel, attribute: .trailing, multiplier: 1, constant: 8)
            containerView.addConstraints([leadingConstraint, trailingConstraint])
        }())
        ({
            let spacingConstraint = NSLayoutConstraint(item: messageLabel, attribute: .top, relatedBy: .equal, toItem: activityIndicatorView, attribute: .bottom, multiplier: 1, constant: activityData.messageSpacing)
            containerView.addConstraint(spacingConstraint)
        }())
        guard let keyWindow = RK_keyWindow else { return }
        keyWindow.addSubview(containerView)
        ({
            let leadingConstraint = NSLayoutConstraint(item: keyWindow, attribute: .leading, relatedBy: .equal, toItem: containerView, attribute: .leading, multiplier: 1, constant: 0)
            let trailingConstraint = NSLayoutConstraint(item: keyWindow, attribute: .trailing, relatedBy: .equal, toItem: containerView, attribute: .trailing, multiplier: 1, constant: 0)
            let topConstraint = NSLayoutConstraint(item: keyWindow, attribute: .top, relatedBy: .equal, toItem: containerView, attribute: .top, multiplier: 1, constant: 0)
            let bottomConstraint = NSLayoutConstraint(item: keyWindow, attribute: .bottom, relatedBy: .equal, toItem: containerView, attribute: .bottom, multiplier: 1, constant: 0)
            keyWindow.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
        }())
    }
    fileprivate func hide(_ fadeOutAnimation: FadeOutAnimation?) {
        guard let keyWindow = RK_keyWindow else { return }
        for item in keyWindow.subviews where item.restorationIdentifier == restorationIdentifier {
            if let fadeOutAnimation = fadeOutAnimation {
                fadeOutAnimation(item) {
                    item.removeFromSuperview()
                }
            } else {
                item.removeFromSuperview()
            }
        }
    }
}
